﻿// author : David Janacek, dj-soft@seznam.cz, 2013-12-13
// company: Asseco Solution, a.s. CZ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Tools.AIDCCodes.Common;

namespace Noris.Tools.AIDCCodes.QrCode
{
    // This file contain classes for data manipulation in generating specific code (char buffer, matrix, QR code descriptor, ...)
    #region class QCode: průběžná data popisující vznikající QR kód
    /// <summary>
    /// QCode: průběžná data popisující vznikající QR kód
    /// </summary>
    internal class QCode : IDisposable
    {
        #region Konstrukce
        internal QCode(string content, QVersion qVersion, QErrorCorrector qErrorCorrector, QMode qMode)
        {
            this.Content = content;
            this.QVersion = qVersion;
            this.QEcBlockSet = qVersion.GetEcBlockSet(qErrorCorrector.Level);
            this.QErrorCorrector = qErrorCorrector;
            this.QMode = qMode;
            this._CalculateInfo();
        }
        /// <summary>
        /// Calculate and store in this properties all need values from this.QVersion and this.QEcBlockSet
        /// </summary>
        private void _CalculateInfo()
        {
            this.NumRSBlocks = this.QEcBlockSet.NumBlocks;                // this.QEcBockSet.NumBlocks = this.QEcBockSet.Sum(this.Blocks.Count), cache result in property
            this.NumDataBytes = this.QVersion.TotalCodewords - this.QEcBlockSet.TotalECCodewords;

            // Calculate values for ErrorCorrection interleave block (Encoder.CreateEcInterleavedBuffer()), values are for :
            // See table 12 in 8.5.1 of JISX0510:2004 (p.30)
            int numTotalBytes = this.NumTotalBytes;                       // 196
            int numDataBytes = this.NumDataBytes;                         //  66
            int numRSBlocks = this.NumRSBlocks;                           //   5

            int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;        // 196 % 5 = 1
            int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;  // 5 - 1 = 4
            int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;      // 196 / 5 = 39
            int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;        // 39 + 1 = 40
            int numDataBytesInGroup1 = numDataBytes / numRSBlocks;        // 66 / 5 = 13
            int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;          // 13 + 1 = 14

            QCodeRsBlockData rsg1 = new QCodeRsBlockData(1, numRsBlocksInGroup1, numTotalBytesInGroup1, numDataBytesInGroup1);
            QCodeRsBlockData rsg2 = new QCodeRsBlockData(2, numRsBlocksInGroup2, numTotalBytesInGroup2, numDataBytesInGroup2);

            // Check results:
            if (rsg1.BytesInBlockEC != rsg2.BytesInBlockEC)
                throw new InvalidProgramException("QCode._CalculateInfo() error: EC bytes mismatch");
            if ((rsg1.BlocksCount + rsg2.BlocksCount) != numRSBlocks)
                throw new InvalidProgramException("QCode._CalculateInfo() error: RS blocks mismatch");
            if ((rsg1.BytesInGroupTotal + rsg2.BytesInGroupTotal) != numTotalBytes)
                throw new InvalidProgramException("QCode._CalculateInfo() error: Total bytes mismatch");
            this.RsBlockGroup1 = rsg1;
            this.RsBlockGroup2 = rsg2;
        }
        void IDisposable.Dispose()
        {
            this.QVersion = null;
            this.QEcBlockSet = null;
            this.QErrorCorrector = null;
            this.QMode = null;
            this.RsBlockGroup1 = null;
            this.RsBlockGroup2 = null;
            this.MatrixInfo = null;
        }
        #endregion
        #region Property
        /// <summary>Text content</summary>
        internal string Content { get; private set; }
        /// <summary>Version of this QR code</summary>
        internal QVersion QVersion { get; private set; }
        /// <summary>Error Correction BlockSet definition for current version and ErrorCorrectorLevel</summary>
        internal QEcBlockSet QEcBlockSet { get; private set; }
        /// <summary>Error Corrector object</summary>
        internal QErrorCorrector QErrorCorrector { get; private set; }
        /// <summary>Current mode</summary>
        internal QMode QMode { get; private set; }

        /// <summary>=this.QVersion.TotalCodewords</summary>
        internal int NumTotalBytes { get { return this.QVersion.TotalCodewords; } }
        /// <summary>=this.QEcBockSet.TotalECCodewords</summary>
        internal int NumECBytes { get { return this.QEcBlockSet.TotalECCodewords; } }
        /// <summary>=this.QVersion.DimensionForVersion</summary>
        internal int MatrixWidth { get { return this.QVersion.DimensionForVersion; } }

        /// <summary>=this.QVersion.TotalCodewords</summary>
        internal int NumDataBytes { get; private set; }
        /// <summary>=this.QEcBockSet.NumBlocks</summary>
        internal int NumRSBlocks { get; private set; }
        /// <summary>Description of ErrorCorrection blocks, in group 1 (Count of blocks, count of data bytes, count of EC bytes)</summary>
        internal QCodeRsBlockData RsBlockGroup1 { get; private set; }
        /// <summary>Description of ErrorCorrection blocks, in group 2 (Count of blocks, count of data bytes, count of EC bytes)</summary>
        internal QCodeRsBlockData RsBlockGroup2 { get; private set; }
        /// <summary>Max(RsBlockGroup1.BytesInBlockData, RsBlockGroup2.BytesInBlockData)</summary>
        internal int RsBlockDataBytesMax { get { return (this.RsBlockGroup1.BytesInBlockData > this.RsBlockGroup2.BytesInBlockData ? this.RsBlockGroup1.BytesInBlockData : this.RsBlockGroup2.BytesInBlockData); } }
        /// <summary>Max(RsBlockGroup1.BytesInBlockData, RsBlockGroup2.BytesInBlockData)</summary>
        internal int RsBlockEcBytesMax { get { return (this.RsBlockGroup1.BytesInBlockEC > this.RsBlockGroup2.BytesInBlockEC ? this.RsBlockGroup1.BytesInBlockEC : this.RsBlockGroup2.BytesInBlockEC); } }
        /// <summary>Mask pattern</summary>
        internal int MaskPattern { get { return (this.MatrixInfo == null ? 0 : this.MatrixInfo.MatrixPattern); } }
        /// <summary>Bit Matrix</summary>
        internal QMatrix Matrix { get { return (this.MatrixInfo == null ? null : this.MatrixInfo.Matrix); } }
        /// <summary>Matrix, version and penalty</summary>
        internal QMatrixInfo MatrixInfo { get; set; }
        #endregion
        #region Data searching
        /// <summary>
        /// Return adequate QCodeRsBlockData for block at index (rsBlockIndex)
        /// </summary>
        /// <param name="rsBlockIndex"></param>
        /// <returns></returns>
        internal QCodeRsBlockData GetRsBlockInfo(int rsBlockIndex)
        {
            if (rsBlockIndex < 0) throw new ArgumentOutOfRangeException("rsBlock", "QCode.GetRsBlockInfo(): index rsBlock can not be a negative number.");
            if (rsBlockIndex < this.RsBlockGroup1.BlocksCount) return this.RsBlockGroup1;
            if (rsBlockIndex < this.NumRSBlocks) return this.RsBlockGroup2;
            throw new ArgumentOutOfRangeException("rsBlock", "QCode.GetRsBlockInfo(): index rsBlock is out of count this.NumRSBlocks.");
        }
        #endregion
    }
    /// <summary>
    /// Description of one ReedSolomon ErrorCorrection block (Count of blocks, count of data bytes, count of EC bytes)
    /// </summary>
    internal class QCodeRsBlockData
    {
        internal QCodeRsBlockData(int groupType, int blocksCount, int bytesInBlockTotal, int bytesInBlockData)
        {
            this.GroupType = groupType;
            this.BlocksCount = blocksCount;
            this.BytesInBlockData = bytesInBlockData;
            this.BytesInBlockEC = (bytesInBlockTotal - bytesInBlockData);
        }
        public override string ToString()
        {
            return "Group " + this.GroupType.ToString() + ": BlocksCount=" + this.BlocksCount.ToString() + "; BytesInBlockData=" + this.BytesInBlockData.ToString() + "; BytesInBlockEC=" + this.BytesInBlockEC.ToString();
        }
        /// <summary>Type of this block (1 or 2)</summary>
        internal int GroupType { get; private set; }
        /// <summary>Count of block of this type </summary>
        internal int BlocksCount { get; private set; }
        /// <summary>Count of Data bytes in one this block</summary>
        internal int BytesInBlockData { get; private set; }
        /// <summary>Count of EC bytes in one this block</summary>
        internal int BytesInBlockEC { get; private set; }
        /// <summary>Count of all bytes in one this block (total bytes = data + EC)</summary>
        internal int BytesInBlockTotal { get { return this.BytesInBlockData + this.BytesInBlockEC; } }
        /// <summary>Count of Data bytes in all of this block (BlocksCount * BytesInBlockData)</summary>
        internal int BytesInGroupData { get { return this.BlocksCount * this.BytesInBlockData; } }
        /// <summary>Count of EC bytes in all of this block (BlocksCount * BytesInBlockEC)</summary>
        internal int BytesInGroupEC { get { return this.BlocksCount * this.BytesInBlockEC; } }
        /// <summary>Count of all bytes in all of this block (total bytes = data + EC) (BlocksCount * BytesInBlockTotal)</summary>
        internal int BytesInGroupTotal { get { return this.BlocksCount * this.BytesInBlockTotal; } }
    }
    #endregion
    #region class QRsBlock: data k výpočtu Reed-Solomon opravných kódů
    internal class QRsBlock
    {
        internal QRsBlock(int rsBlockIndex, int dataPointer, QCodeRsBlockData info)
        {
            this.RsBlockIndex = rsBlockIndex;
            this.DataPointer = dataPointer;
            this.Info = info;
        }
        public override string ToString()
        {
            return "QRsBlock[" + this.RsBlockIndex.ToString() + "]: DataPointer=" + this.DataPointer.ToString() + "; Info=" + this.Info.ToString();
        }
        /// <summary>Index of this block</summary>
        internal int RsBlockIndex { get; private set; }
        /// <summary>Pointer to input data buffer from where was data get</summary>
        internal int DataPointer { get; private set; }
        /// <summary>Info with description of this RS block</summary>
        internal QCodeRsBlockData Info { get; private set; }
        /// <summary>Data</summary>
        internal int[] Data { get; set; }
        /// <summary>EC values</summary>
        internal int[] Ec { get; set; }
    }
    #endregion
    #region class QMatrixInfo: one QMatrix, with specified MatrixPattern and corresponding Penalty value.
    /// <summary>
    /// QMatrixInfo: one QMatrix, with specified MatrixPattern and corresponding Penalty value.
    /// </summary>
    internal class QMatrixInfo
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="matrixPattern"></param>
        /// <param name="matrixWidth"></param>
        internal QMatrixInfo(int matrixPattern, int matrixWidth)
        {
            this.MatrixPattern = matrixPattern;
            this.Matrix = new QMatrix(matrixWidth, matrixWidth);
        }
        /// <summary>
        /// MatrixPattern number (0 ÷ 7)
        /// </summary>
        internal int MatrixPattern { get; private set; }
        /// <summary>
        /// Content of code
        /// </summary>
        internal QMatrix Matrix { get; private set; }
        /// <summary>
        /// Penalty for this QMatrix by rules
        /// </summary>
        internal int Penalty { get; set; }
    }
    #endregion
    #region class Crc32: Implements a 32-bit CRC hash algorithm compatible with Zip etc.
    /// <summary>
    /// Implements a 32-bit CRC hash algorithm compatible with Zip etc.
    /// </summary>
    /// <remarks>
    /// Crc32 should only be used for backward compatibility with older file formats
    /// and algorithms. It is not secure enough for new applications.
    /// If you need to call multiple times for the same data either use the HashAlgorithm
    /// interface or remember that the result of one Compute call needs to be ~ (XOR) before
    /// being passed in as the seed for the next Compute call.
    /// </remarks>
    /// <example>
    /// Calculate CRC32 of file:
    /// using (FileStream fs = File.Open("c:\\myfile.txt", FileMode.Open))
    ///     foreach (byte b in crc32.Compute(fs)) hash += b.ToString("x2").ToLower();
    /// </example>
    public sealed class Crc32 : System.Security.Cryptography.HashAlgorithm
    {
        public override int HashSize { get { return 32; } }
        public static UInt32 Compute(byte[] buffer)
        {
            return Compute(DefaultSeed, buffer);
        }
        public static UInt32 Compute(UInt32 seed, byte[] buffer)
        {
            return Compute(DefaultPolynomial, seed, buffer);
        }
        public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
        {
            return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
        }

        public const UInt32 DefaultPolynomial = 0xedb88320u;
        public const UInt32 DefaultSeed = 0xffffffffu;
        public Crc32()
            : this(DefaultPolynomial, DefaultSeed)
        {
        }
        public Crc32(UInt32 polynomial, UInt32 seed)
        {
            table = InitializeTable(polynomial);
            this.seed = hash = seed;
        }
        public override void Initialize()
        {
            hash = seed;
        }
        
        protected override void HashCore(byte[] buffer, int start, int length)
        {
            hash = CalculateHash(table, hash, buffer, start, length);
        }
        protected override byte[] HashFinal()
        {
            var hashBuffer = UInt32ToBigEndianBytes(~hash);
            HashValue = hashBuffer;
            return hashBuffer;
        }

        private static UInt32[] defaultTable;
        private readonly UInt32 seed;
        private readonly UInt32[] table;
        private UInt32 hash;
        private static UInt32[] InitializeTable(UInt32 polynomial)
        {
            if (polynomial == DefaultPolynomial && defaultTable != null)
                return defaultTable;

            var createTable = new UInt32[256];
            for (var i = 0; i < 256; i++)
            {
                var entry = (UInt32)i;
                for (var j = 0; j < 8; j++)
                    if ((entry & 1) == 1)
                        entry = (entry >> 1) ^ polynomial;
                    else
                        entry = entry >> 1;
                createTable[i] = entry;
            }

            if (polynomial == DefaultPolynomial)
                defaultTable = createTable;

            return createTable;
        }
        private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList<byte> buffer, int start, int size)
        {
            var crc = seed;
            for (var i = start; i < size - start; i++)
                crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
            return crc;
        }
        private static byte[] UInt32ToBigEndianBytes(UInt32 uint32)
        {
            var result = BitConverter.GetBytes(uint32);

            if (BitConverter.IsLittleEndian)
                Array.Reverse(result);

            return result;
        }
    }
    #endregion
}
